package com.yml.mybatis.gererator.service;

import com.yml.mybatis.gererator.config.*;
import com.yml.mybatis.gererator.constant.OrmType;
import com.yml.mybatis.gererator.meta.ColumnInfo;
import com.yml.mybatis.gererator.meta.DataSourceInfo;
import com.yml.mybatis.gererator.meta.TableInfo;
import com.yml.mybatis.gererator.util.FreemarkerUtils;
import com.yml.mybatis.gererator.util.StrUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * 自动生成代码类
 *
 * @author YML
 * @since 2023-07-21 10:21
 */
public class AutoGenerator {

    private final DataSourceInfo dataSource;

    private GlobalConfig globalConfig;
    private EntityConfig entityConfig;
    private MapperConfig mapperConfig;
    private ServiceConfig serviceConfig;

    private ControllerConfig controllerConfig;

    private  String root;

    /** 模板变量 **/
    private final Map<String,Object> dataMap = new HashMap<>();

    private AutoGenerator(DataSourceInfo dataSource){
        this.dataSource = dataSource;
    }

    public static AutoGenerator create(DataSourceInfo dataSource,
                                       Consumer<GlobalConfig> globalConfigConsumer,
                                       Consumer<EntityConfig> entityConfigConsumer,
                                       Consumer<MapperConfig> mapperConfigConsumer,
                                       Consumer<ServiceConfig> serviceConfigConsumer,
                                       Consumer<ControllerConfig> controllerConfigConsumer
                                        ) {
        AutoGenerator generator = new AutoGenerator(dataSource);
        GlobalConfig globalConfig = new GlobalConfig();
        if (globalConfigConsumer != null) {
            globalConfigConsumer.accept(globalConfig);
        }
        EntityConfig entityConfig = new EntityConfig();
        if (entityConfigConsumer != null) {
            entityConfigConsumer.accept(entityConfig);
        }
        MapperConfig mapperConfig = new MapperConfig();
        if (mapperConfigConsumer != null) {
            mapperConfigConsumer.accept(mapperConfig);
        }
        ServiceConfig serviceConfig = new ServiceConfig();
        if (serviceConfigConsumer != null) {
            serviceConfigConsumer.accept(serviceConfig);
        }
        ControllerConfig controllerConfig = new ControllerConfig();
        if (controllerConfigConsumer != null) {
            controllerConfigConsumer.accept(controllerConfig);
            generator.controllerConfig = controllerConfig;
        }
        generator.globalConfig = globalConfig;
        generator.entityConfig = entityConfig;
        generator.mapperConfig = mapperConfig;
        generator.serviceConfig = serviceConfig;
        generator.build();
        return generator;
    }

    private void build() {
        String rootPath = "";
        if (StringUtils.isBlank(globalConfig.getRoot())){
            globalConfig.setRoot(System.getProperty("user.dir"));
        }
        rootPath = StringUtils.isNotBlank(globalConfig.getModulePath())
                ? globalConfig.getRoot() + File.separator + globalConfig.getModulePath()
                : globalConfig.getRoot();
        dataMap.put("rootPath", rootPath);
        dataMap.put("ormType", globalConfig.getOrmType());
        dataMap.put("author", globalConfig.getAuthor());
        dataMap.put("enableLombok", globalConfig.isEnableLombok());
        dataMap.put("enableSwagger3", globalConfig.isEnableSwagger3());
        dataMap.put("enableColumnComment", globalConfig.isEnableColumnComment());
        dataMap.put("templatePrefix", globalConfig.isEnableKotlin() ? "kt-" : "");
        dataMap.put("fileType", globalConfig.isEnableKotlin() ? ".kt" : ".java");
        dataMap.put("codePath", rootPath + File.separator + (globalConfig.isEnableKotlin() ? globalConfig.getKotlinPath() : globalConfig.getJavaPath()));
        if (StringUtils.isNotBlank(globalConfig.getDateFormat())) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(globalConfig.getDateFormat());
            dataMap.put("date",formatter.format(LocalDateTime.now()));
        }
        dataMap.put("enableMyBatisPlus", globalConfig.getOrmType() == OrmType.MYBATIS_PLUS);

        // 实体类
        dataMap.put("entityPackage", globalConfig.getBasePackage() +
                (StringUtils.isBlank(entityConfig.getEntityPackage()) ? "" : ("." + entityConfig.getEntityPackage()))
                );
        dataMap.put("generateConvert", entityConfig.isEnableGenerateConvert());
        // mapper
        dataMap.put("mapperPackage", globalConfig.getBasePackage() +
                        (StringUtils.isBlank(mapperConfig.getMapperPackage()) ? "" : ("." + mapperConfig.getMapperPackage()))
                );
        dataMap.put("withSelective",mapperConfig.isEnableSelective());
        // service
        dataMap.put("servicePackage", globalConfig.getBasePackage() +
                (StringUtils.isBlank(serviceConfig.getServicePackage()) ? "" : ("." + serviceConfig.getServicePackage()))
        );
        dataMap.put("implPackage", globalConfig.getBasePackage() +
                (StringUtils.isBlank(serviceConfig.getImplPackage()) ? "" : ("." + serviceConfig.getImplPackage()))
        );
        // controller
        if (controllerConfig != null && controllerConfig.isEnableGenerateController()){
            dataMap.put("modelPackage", globalConfig.getBasePackage() +
                    (StringUtils.isBlank(controllerConfig.getModelPackage()) ? "" : ("." + controllerConfig.getModelPackage()))
            );
            dataMap.put("apiPackage", globalConfig.getBasePackage() +
                    (StringUtils.isBlank(controllerConfig.getApiPackage()) ? "" : ("." + controllerConfig.getApiPackage()))
            );
            dataMap.put("controllerPackage", globalConfig.getBasePackage() +
                    (StringUtils.isBlank(controllerConfig.getControllerPackage()) ? "" : ("." + controllerConfig.getControllerPackage()))
            );
        }
    }

    /**
     * 代码生成
     * @author YML
     * @since 2024/9/26 16:21
     * @param type: 1：生成文件，2：返回归类好的字符串
     */
    public Map<String,Map<String,Object>> execute(int type) throws Exception {
        this.dataSource.getConnection();
        Map<String,Map<String,Object>> result = new HashMap<>();
        try {
            ITableService service = TableFactory.getService(this.dataSource);
            List<TableInfo> tableInfos = service.queryTables(entityConfig.getTables());
            for (TableInfo table : tableInfos) {
                List<ColumnInfo> columnInfos = service.queryColumns(table.getId());
                dataMap.put("tableName", table.getTableName());
                formatTableName(table);
                dataMap.put("tableComment", table.getComment());
                // 查找主键
                List<ColumnInfo> normalColumnList = new ArrayList<>();
                List<ColumnInfo> primaryColumnList = new ArrayList<>();
                columnInfos.forEach(v -> {
                    if (StringUtils.equals(v.getPrimaryKey(), "1")) {
                        primaryColumnList.add(v);
                    } else {
                        normalColumnList.add(v);
                    }
                });
                dataMap.put("primaryColumn", primaryColumnList);
                dataMap.put("normalColumn", normalColumnList);
                dataMap.put("columns", columnInfos);
                generatorImportList(columnInfos, dataMap);


                Map<String,Object> meta = new HashMap<>();
                meta.put("tableName", table.getTableName());
                meta.put("tableFormatName", table.getTableFormatName());
                Map<String, Object> entityMap = generatorEntity(table, type);
                if (!entityMap.isEmpty()){
                    meta.putAll(entityMap);
                }
                Map<String, Object> mapperMap = generatorMapper(table, type);
                if (!mapperMap.isEmpty()){
                    meta.putAll(mapperMap);
                }
                Map<String, Object> serviceMap = generatorService(table, type);
                if (!serviceMap.isEmpty()){
                    meta.putAll(serviceMap);
                }
                if (controllerConfig != null && controllerConfig.isEnableGenerateController()){
                    Map<String, Object> apiMap = generatorModelAndControllerWithApi(table, type);
                    if (!apiMap.isEmpty()){
                        meta.putAll(apiMap);
                    }
                }
                result.put(table.getTableName(), meta);
            }
            return result;
        } finally {
            this.dataSource.close();
        }
    }

    private void formatTableName(TableInfo table) {
        if (null != globalConfig.getIgnorePrefix()) {
            for (String ignorePrefix : globalConfig.getIgnorePrefix()) {
                if (!table.getTableName().startsWith(ignorePrefix)) {
                    continue;
                }
                String name = table.getTableName().replace(ignorePrefix, "");
                table.setTableFormatName(name);
                break;
            }
        }
        if (StringUtils.isBlank(table.getTableFormatName())){
            table.setTableFormatName(table.getTableName());
        }
    }

    private void generatorImportList(List<ColumnInfo> columnInfos, Map<String, Object> dataMap) {
        List<String> importList = new ArrayList<>();
        columnInfos.forEach(v -> {
            if (StringUtils.equalsIgnoreCase(v.getJavaType(), "LocalDateTime")) {
                importList.add("java.time.LocalDateTime");
            }
            if (StringUtils.equalsIgnoreCase(v.getJavaType(), "LocalDate")) {
                importList.add("java.time.LocalDate");
            }
            if (StringUtils.equalsIgnoreCase(v.getJavaType(), "BigDecimal")) {
                importList.add("java.math.BigDecimal");
            }
            if (StringUtils.equalsIgnoreCase(v.getJavaType(), "Integer") && globalConfig.isEnableKotlin()){
                v.setJavaType("Int");
            }
        });
        if (globalConfig.getOrmType() == OrmType.MYBATIS_PLUS) {
            importList.add("com.baomidou.mybatisplus.annotation.TableField");
            importList.add("com.baomidou.mybatisplus.annotation.TableName");
            importList.add("com.baomidou.mybatisplus.annotation.TableId");
        }
        if (globalConfig.isEnableSwagger3()) {
            importList.add("io.swagger.v3.oas.annotations.media.Schema");
        }
        if (globalConfig.isEnableLombok()) {
            importList.add("lombok.Getter");
            importList.add("lombok.Setter");
            importList.add("lombok.experimental.Accessors");
        }
        dataMap.put("importList", importList.stream().distinct().collect(Collectors.toList()));
    }

    private Map<String, Object> generatorEntity(TableInfo table,int type) throws Exception {
        Map<String, Object> result = new HashMap<>();
        String camelTableName = StrUtils.camelCase(table.getTableFormatName(), true);
        String entityName = String.format(entityConfig.getEntityFormat(), camelTableName);
        dataMap.put("camelTableName", camelTableName);
        dataMap.put("entityName", entityName);
        dataMap.put("entityFullName", dataMap.get("entityPackage") + "." + entityName);
        String entityFtl = (globalConfig.getOrmType() == OrmType.MYBATIS_PLUS ? "mybatis-plus/" : "mybatis/")
                + dataMap.get("templatePrefix") + "entity.ftl";
        if (type == 1){
            String filePath = dataMap.get("codePath") + File.separator
                    + dataMap.get("entityFullName").toString().replace(".",File.separator)
                    + dataMap.get("fileType");
            FreemarkerUtils.createFileFromTemplate(Path.of(filePath), dataMap, entityFtl);
            System.out.println("===> 实体类生成完成！文件地址：" + filePath);
        }else {
            result.put("entity", FreemarkerUtils.createStringFromTemplate(dataMap, entityFtl));
        }
        result.put("camelTableName", camelTableName);
        return result;
    }

    private Map<String, Object> generatorMapper(TableInfo table, int type) throws Exception {
        Map<String, Object> result = new HashMap<>();
        String mapperName = StrUtils.camelCase(table.getTableFormatName(), true);
        mapperName = String.format(mapperConfig.getMapperFormat(), mapperName);
        dataMap.put("mapperName", mapperName);
        dataMap.put("mapperFullName", dataMap.get("mapperPackage") + "." + mapperName);
        String mapperFtl = (globalConfig.getOrmType() == OrmType.MYBATIS_PLUS ? "mybatis-plus/" : "mybatis/")
                + dataMap.get("templatePrefix") +  "mapper.ftl";
        if (type == 1){
            String mapperFilePathDir = dataMap.get("codePath") + File.separator
                    + dataMap.get("mapperFullName").toString().replace(".",File.separator)
                    + dataMap.get("fileType");
            FreemarkerUtils.createFileFromTemplate(Path.of(mapperFilePathDir), dataMap, mapperFtl);
            System.out.println("===> Mapper生成完成！文件地址：" + mapperFilePathDir);
        }else{
            result.put("mapper",FreemarkerUtils.createStringFromTemplate(dataMap, mapperFtl));
        }

        String xmlFtl = (globalConfig.getOrmType() == OrmType.MYBATIS_PLUS ? "mybatis-plus/" : "mybatis/")
                + dataMap.get("templatePrefix") +  "mapperXml.ftl";
        if (type == 1){
            String xmlPath = StringUtils.isBlank(mapperConfig.getMapperXmlPackage()) ?
                    globalConfig.getMapperXmlPath() :
                    globalConfig.getMapperXmlPath() + File.separator + mapperConfig.getMapperXmlPackage().replace(".","/");
            Path xml = Path.of(dataMap.get("rootPath") + File.separator + xmlPath + File.separator + mapperName + ".xml");
            FreemarkerUtils.createFileFromTemplate(xml, dataMap, xmlFtl);
            System.out.println("===> XML生成完成！文件地址：" + xml);
        }else{
            result.put("mapperXml",FreemarkerUtils.createStringFromTemplate(dataMap, xmlFtl));
        }
        return result;
    }

    private Map<String, Object> generatorService(TableInfo table, int type)
            throws Exception {
        Map<String, Object> result = new HashMap<>();
        String serviceName = StrUtils.camelCase(table.getTableFormatName(), true);
        serviceName = String.format(serviceConfig.getServiceFormat(), serviceName);
        dataMap.put("serviceName", serviceName);
        dataMap.put("serviceFullName", dataMap.get("servicePackage") + "." + serviceName);
        String serviceFtl = (globalConfig.getOrmType() == OrmType.MYBATIS_PLUS ? "mybatis-plus/" : "mybatis/")
                + dataMap.get("templatePrefix") + "service.ftl";
        if (type == 1){
            String filePathDir = dataMap.get("codePath") + File.separator
                    + dataMap.get("serviceFullName").toString().replace(".",File.separator)
                    + dataMap.get("fileType");
            FreemarkerUtils.createFileFromTemplate(Path.of(filePathDir), dataMap, serviceFtl);
            System.out.println("===> Service生成完成！文件地址：" + filePathDir);
        }else{
            result.put("service",FreemarkerUtils.createStringFromTemplate(dataMap, serviceFtl));
        }

        String implName = StrUtils.camelCase(table.getTableFormatName(), true);
        implName = String.format(serviceConfig.getImplFormat(), implName);
        dataMap.put("implName", implName);
        dataMap.put("implFullName", dataMap.get("implPackage") + "." + implName);
        String implFtl = (globalConfig.getOrmType() == OrmType.MYBATIS_PLUS ? "mybatis-plus/" : "mybatis/")
                + dataMap.get("templatePrefix") + "serviceImpl.ftl";
        if (type == 1){
            String filePath = dataMap.get("codePath") + File.separator
                    + dataMap.get("implFullName").toString().replace(".",File.separator)
                    + dataMap.get("fileType");
            FreemarkerUtils.createFileFromTemplate(Path.of(filePath), dataMap, implFtl);
            System.out.println("===> ServiceImpl生成完成！文件地址：" + filePath);
        }else{
            result.put("serviceImpl",FreemarkerUtils.createStringFromTemplate(dataMap, implFtl));
        }
        return result;
    }

    private Map<String, Object> generatorModelAndControllerWithApi(TableInfo table, int type) throws Exception {
        Map<String, Object> result = new HashMap<>();
        String modelName = StrUtils.camelCase(table.getTableFormatName(), true);
        modelName = String.format(controllerConfig.getModelFormat(), modelName);
        dataMap.put("modelName", modelName);
        dataMap.put("modelFullName", dataMap.get("modelPackage") + "." + modelName);
        String modelFtl = "api/" + dataMap.get("templatePrefix") + "model.ftl";
        if (type == 1){
            String filePath = dataMap.get("codePath") + File.separator
                    + dataMap.get("modelFullName").toString().replace(".",File.separator)
                    + dataMap.get("fileType");
            FreemarkerUtils.createFileFromTemplate(Path.of(filePath), dataMap, modelFtl);
            System.out.println("===> Model生成完成！文件地址：" + filePath);
        }else{
            result.put("model",FreemarkerUtils.createStringFromTemplate(dataMap, modelFtl));
        }

        String apiName = StrUtils.camelCase(table.getTableFormatName(), true);
        apiName = String.format(controllerConfig.getApiFormat(), apiName);
        dataMap.put("apiName", apiName);
        dataMap.put("apiFullName", dataMap.get("apiPackage") + "." + apiName);
        String apiFtl = "api/" + dataMap.get("templatePrefix") + "api.ftl";
        if (type == 1){
            String filePath = dataMap.get("codePath") + File.separator
                    + dataMap.get("apiFullName").toString().replace(".",File.separator)
                    + dataMap.get("fileType");
            FreemarkerUtils.createFileFromTemplate(Path.of(filePath), dataMap, apiFtl);
            System.out.println("===> Api生成完成！文件地址：" + filePath);
        }else{
            result.put("api",FreemarkerUtils.createStringFromTemplate(dataMap, apiFtl));
        }

        String controllerName = StrUtils.camelCase(table.getTableFormatName(), true);
        controllerName = String.format(controllerConfig.getControllerFormat(), controllerName);
        dataMap.put("controllerName", controllerName);
        dataMap.put("controllerFullName", dataMap.get("controllerPackage") + "." + controllerName);
        String controllerFtl = "api/" + dataMap.get("templatePrefix") + "controller.ftl";
        if (type == 1){
            String filePath = dataMap.get("codePath") + File.separator
                    + dataMap.get("controllerFullName").toString().replace(".",File.separator)
                    + dataMap.get("fileType");
            FreemarkerUtils.createFileFromTemplate(Path.of(filePath), dataMap, controllerFtl);
            System.out.println("===> Controller生成完成！文件地址：" + filePath);
        }else{
            result.put("controller",FreemarkerUtils.createStringFromTemplate(dataMap, controllerFtl));
        }
        return result;
    }
}
